/**
 * 
 */
package com.lion.jacoco.manager.service.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.InetAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.jacoco.core.analysis.Analyzer;
import org.jacoco.core.analysis.CoverageBuilder;
import org.jacoco.core.analysis.IBundleCoverage;
import org.jacoco.core.analysis.ISourceFileCoverage;
import org.jacoco.core.data.ExecutionDataReader;
import org.jacoco.core.data.ExecutionDataStore;
import org.jacoco.core.data.SessionInfoStore;
import org.jacoco.core.internal.analysis.SourceFileCoverageImpl;
import org.jacoco.core.runtime.RemoteControlReader;
import org.jacoco.core.runtime.RemoteControlWriter;
import org.jacoco.report.FileMultiReportOutput;
import org.jacoco.report.IReportVisitor;
import org.jacoco.report.ISourceFileLocator;
import org.jacoco.report.html.HTMLFormatter;
import org.springframework.beans.factory.annotation.Autowired;

import com.lion.jacoco.common.GlobalVariable;
import com.lion.jacoco.common.JacocoConstant;
import com.lion.jacoco.dao.dataobject.AppConfigDO;
import com.lion.jacoco.manager.service.JacocoService;

/**
 * JacocoServiceImpl.java. 2011-11-28下午4:22:22 @author LionBule.
 */
public class JacocoServiceImpl implements JacocoService {

    @Override
    public void createReport(AppConfigDO appConfigDO) {
        if (appConfigDO == null) {
            throw new NullPointerException();
        }

        try {
            String baseDir = appConfigDO.getCodePath(globalVariable.getCodePath());

            //get jacoco.exec from client
            exec(baseDir, appConfigDO);

            //create report
            create(baseDir, appConfigDO);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    private void exec(String baseDir, AppConfigDO appConfigDO) throws IOException {

        final FileOutputStream localFile = new FileOutputStream(new File(
                appConfigDO.getExecFile(globalVariable.getCodePath())));
        final RemoteControlWriter localWriter = new RemoteControlWriter(localFile);

        // Open a socket to the coverage agent:
        final Socket socket = new Socket(InetAddress.getByName(appConfigDO.getIp()),
                JacocoConstant.PORT);
        final RemoteControlWriter writer = new RemoteControlWriter(socket.getOutputStream());
        final RemoteControlReader reader = new RemoteControlReader(socket.getInputStream());
        reader.setSessionInfoVisitor(localWriter);
        reader.setExecutionDataVisitor(localWriter);

        // Send a dump command and read the response:
        writer.visitDumpCommand(true, false);
        reader.read();

        socket.close();
        localFile.close();
    }

    private void create(String baseDir, AppConfigDO appConfigDO) throws IOException {
        String title = new File(baseDir).getName();

        List<SourceDO> allPaths = findSourceAndClass(baseDir);
        File executionDataFile = new File(appConfigDO.getExecFile(globalVariable.getCodePath()));

        Map<String, File> sourceFiles = new HashMap<String, File>();
        File reportDirectory = new File(appConfigDO.getReportPath(globalVariable.getReportPath()));
        ExecutionDataStore executionDataStore = new ExecutionDataStore();
        SessionInfoStore sessionInfoStore = new SessionInfoStore();

        loadExecutionData(executionDataStore, sessionInfoStore, executionDataFile);

        final IBundleCoverage bundleCoverage = analyzeStructure(title, executionDataStore,
                allPaths, sourceFiles);

        final HTMLFormatter htmlFormatter = new HTMLFormatter();
        final IReportVisitor visitor = htmlFormatter.createVisitor(new FileMultiReportOutput(
                reportDirectory));
        visitor.visitInfo(sessionInfoStore.getInfos(), executionDataStore.getContents());
        visitor.visitBundle(bundleCoverage, new LbISourceFileLocator("utf-8", 4, sourceFiles));
        visitor.visitEnd();

    }

    private void loadExecutionData(ExecutionDataStore executionDataStore,
                                   SessionInfoStore sessionInfoStore, File executionDataFile)
            throws IOException {
        final FileInputStream fis = new FileInputStream(executionDataFile);
        final ExecutionDataReader executionDataReader = new ExecutionDataReader(fis);
        executionDataReader.setExecutionDataVisitor(executionDataStore);
        executionDataReader.setSessionInfoVisitor(sessionInfoStore);

        while (executionDataReader.read()) {
        }

        fis.close();
    }

    private IBundleCoverage analyzeStructure(String title, ExecutionDataStore executionDataStore,
                                             List<SourceDO> allPaths, Map<String, File> sourceFiles)
            throws IOException {
        final CoverageBuilder coverageBuilder = new CoverageBuilder();
        final Analyzer analyzer = new Analyzer(executionDataStore, coverageBuilder);

        for (SourceDO sourceDO : allPaths) {
            File classesDirectory = new File(sourceDO.targetPath);
            int count = analyzer.analyzeAll(classesDirectory);
            if (count > 0) {
                Collection<ISourceFileCoverage> packages = coverageBuilder.getSourceFiles();
                for (ISourceFileCoverage p : packages) {
                    SourceFileCoverageImpl _sp = (SourceFileCoverageImpl) p;
                    String _name = p.getPackageName() + "/" + _sp.getName();
                    if (!sourceFiles.containsKey(_name)) {
                        sourceFiles.put(_name, new File(sourceDO.srcPath, _name));
                    }
                }
            }
        }

        return coverageBuilder.getBundle(title);
    }

    private List<SourceDO> findSourceAndClass(String baseDir) {
        List<SourceDO> result = new ArrayList<SourceDO>();
        List<File> baseFiles = new ArrayList<File>();
        findBasePath(new File(baseDir), baseFiles);
        for (File df : baseFiles) {
            String srcPath = new File(df, "main/java").getAbsolutePath();
            String targetPath = new File(df.getParentFile(), "target/classes").getAbsolutePath();
            SourceDO dp = new SourceDO(srcPath, targetPath);
            result.add(dp);
        }
        return result;
    }

    private void findBasePath(File source, List<File> baseFiles) {
        if (!source.isDirectory()) {
            return;
        }

        File[] result = source.listFiles();
        for (File tf : result) {
            if (tf.getName().equals(SRC)) {
                baseFiles.add(tf);
            } else {
                findBasePath(tf, baseFiles);
            }
        }

    }

    private class SourceDO {
        String srcPath;
        String targetPath;

        SourceDO(String srcPath, String targetPath) {
            this.srcPath = srcPath;
            this.targetPath = targetPath;
        }

    }

    class LbISourceFileLocator implements ISourceFileLocator {
        Map<String, File> sourceFiles;
        String            encoding;
        int               tabWidth;

        LbISourceFileLocator(String encoding, int tabWidth, Map<String, File> sourceFiles) {
            this.encoding = encoding;
            this.tabWidth = tabWidth;
            this.sourceFiles = sourceFiles;
        }

        @Override
        public Reader getSourceFile(String packageName, String fileName) throws IOException {
            if (StringUtils.isBlank(packageName) || StringUtils.isBlank(fileName)) {
                return null;
            }
            String name = packageName + "/" + fileName;
            File file = sourceFiles.get(name);
            if (file != null) {
                return new InputStreamReader(new FileInputStream(file), encoding);
            }
            return null;
        }

        @Override
        public int getTabWidth() {
            return tabWidth;
        }

    }

    private static final String SRC = "src";
    @Autowired
    private GlobalVariable      globalVariable;

}
